package taks5;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Bank {
	
	private static final Logger LOGGER = Logger.getLogger(Bank.class.getName());
	
	private static final int THR = 40000;

	private static final int ACC = 5;

	final List<Account> accounts = new ArrayList<Account>();
	
	AtomicInteger transactionsDone = new AtomicInteger();
	AtomicInteger transactionsExpected = new AtomicInteger();
	AtomicInteger WentToWait = new AtomicInteger();
	
	public int totalMoney(){
		int result = 0;
		for(Account account : accounts){
			result += account.money;
		}
		return result;
	}
	
	public void transfer(Account from, Account to, int amount){
		
		from.lock.lock();
		try{
			while(true){
				if(to.lock.tryLock()){
					try{
						makeTransfer(from, to, amount);
						transactionsDone.incrementAndGet();
						break;
					} finally {
						to.isUsed.signalAll();
						to.lock.unlock();
					}
				} else {
					WentToWait.incrementAndGet();
					try {
						from.isUsed.await();
					} catch (InterruptedException e) {
						LOGGER.log(Level.SEVERE, e.getMessage());
					}
				}
			}
		} finally {
			from.isUsed.signalAll();
			from.lock.unlock();
		}
	}
	
	private int makeTransfer(Account from, Account to, int amount){
		return to.deposit(from.withdraw(amount));
	}
	
	public static void main(String[] str){
		
		Bank bank = new Bank();
		
		ExecutorService service = Executors.newCachedThreadPool();
		
		Random r = new Random();
		
		List<User> users = new ArrayList<User>();
		
		for(int i = 0; i < ACC; i++){
			int money = r.nextInt(10000);
			users.add(new User(bank, money));
		}
		
		int totalMoney = bank.totalMoney();
		
		for(int i = 0; i < THR; i++){
			service.execute(new TransactionRunner(bank));
		}
		
		try {
			service.shutdown();
			service.awaitTermination(1, TimeUnit.MINUTES);
		} catch (InterruptedException e) {
			LOGGER.log(Level.SEVERE, e.getMessage());
		}
		
		int totalMoneyAfterTransactions = bank.totalMoney();
		
		System.out.println("Went to wait : " + bank.WentToWait);
		System.out.println("Transactions done : " + bank.transactionsDone + " expected : " + bank.transactionsExpected);
		System.out.println("Balance before : " + totalMoney + " After : " + totalMoneyAfterTransactions);
	}
}

class TransactionRunner implements Runnable{

	Random r = new Random();
	
	Bank bank;
	
	public TransactionRunner(Bank bank) {
		this.bank = bank;
	}
	
	public void run() {
		int fromptr = r.nextInt(bank.accounts.size());
		int toptr = r.nextInt(bank.accounts.size());
		while(toptr == fromptr)
			toptr = r.nextInt(bank.accounts.size());
		
		Account from = bank.accounts.get(fromptr);
		Account to = bank.accounts.get(toptr);
		if(from.money > 100){
			int amount =  r.nextInt(from.money - 100);
			
			bank.transfer(from, to, amount);
			bank.transactionsExpected.incrementAndGet();
		}
	}
	
}
